﻿
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Versioning;
using System.Speech.Synthesis;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using ShellProgressBar;

[assembly:SupportedOSPlatform("windows")]

string currentDir = Environment.CurrentDirectory;
string appDir = AppContext.BaseDirectory;
const string configFileName = "ttsconfig.json";

// Console.WriteLine($"currentDir = { currentDir }"); // 用于计算当前文件路径
// Console.WriteLine($"appDir = { appDir }"); // 可执行程序的地址
// Console.WriteLine(string.Join(", ", args));

// 基本流程
// 1. 读取 config 文件, 解析需要的参数
// 2. 读取小说, 按照 regiex 的格式进行分割生成不同章节
// 3. 安顺序读取章节内容, 生成音频波形文件
// 4. 调用 ffmpeg 生成 mp3

#region 一些json配置

#pragma warning disable CA1869
var w_jsonOpts = new JsonSerializerOptions() {
  WriteIndented = true
};
var r_jsonOpts = new JsonSerializerOptions() {
  PropertyNameCaseInsensitive = true,
};
#pragma warning restore CA1869

#endregion

if (args.Length > 0 && "version".Equals(args[0], StringComparison.OrdinalIgnoreCase)) {  
  Console.WriteLine("version: v0.1.4");
  return;
}

#region 初始化
if (args.Length > 0 && "init".Equals(args[0], StringComparison.OrdinalIgnoreCase)) {  
  var _txtFiles = Directory.GetFiles(currentDir, "*.txt")?.Select(f => Path.GetFileName(f)).ToArray();
  string newConfigPath = Path.Combine(currentDir, configFileName);
  string jsonStr = JsonSerializer.Serialize(new Config {
    Input = _txtFiles?[0] ?? null,
    Thread = Environment.ProcessorCount,
    Speed = 8,
    Encoding = "gb2312"
  }, typeof(Config), w_jsonOpts);
  File.WriteAllText(newConfigPath, jsonStr);
  return;
}
#endregion


#region 读取配置文件
string configPath = Path.Combine(currentDir, configFileName);
if (!File.Exists(configPath)) {
  Console.WriteLine($"配置文件不存在, 执行 { Environment.CommandLine } init");
  return;
}
using Stream confStream = new FileStream(configPath, FileMode.Open);

var conf = await JsonSerializer.DeserializeAsync<Config>(confStream, r_jsonOpts);

if (conf == null) {
  Console.WriteLine("配置文件异常");
  return;
}

if (string.IsNullOrWhiteSpace(conf?.Input)) {
  Console.WriteLine("未指定需要转换的文本 (配置 input 字段)");
  return;
}

#endregion 

#region 创建进度条
using var mainBar = new ProgressBar(2, "生成小说音频", new ProgressBarOptions {
  ForegroundColor = ConsoleColor.Yellow,
  // BackgroundColor = ConsoleColor.DarkYellow,
  ProgressBarOnBottom = true,
  DisplayTimeInRealTime = false,
});
#endregion


#region 读取文件, 生成各个章节
Encoding encoding = conf.Encoding switch {
  "gb2312" or "GB2312" => Encoding.GetEncoding("gb2312"),
  "utf8" or "UTF8" => Encoding.UTF8,
  _ => Encoding.UTF8
};

string filePath = Path.Combine(currentDir, conf.Input);
string outputPath = Path.Combine(currentDir, conf.Output ?? "dest");
if (!Directory.Exists(outputPath)) {
  Directory.CreateDirectory(outputPath);
}
string tmpDir = Path.Combine(currentDir, "tmp");
if (!Directory.Exists(tmpDir)) {
  Directory.CreateDirectory(tmpDir);
}

if (!File.Exists(filePath)) {
  Console.WriteLine($"文件'{ conf.Input }'不存在");
  return;
}

if (args.Length > 0 && "step2".Equals(args[0], StringComparison.OrdinalIgnoreCase)) {
  goto LabelStep1;
}

// 生成
if (!conf.UseRegex) {
 
  // string dest = Path.Combine(tmpDir, conf.Input);
  // File.Copy(filePath, dest);
  using var pbarReadTxt = mainBar.Spawn(1, "读取解析小说文本", new ProgressBarOptions {
    ForegroundColor = ConsoleColor.Green,
    // BackgroundColor = ConsoleColor.DarkGreen,
    ProgressBarOnBottom = true,
    DisplayTimeInRealTime = false,
  });

  var builder = new StringBuilder();
  using var stream = new FileStream(filePath, FileMode.Open);
  using var reader = new StreamReader(stream, encoding);

  int index = conf?.StartIndex ?? 0;
  var name = Path.GetFileNameWithoutExtension(conf!.Input);
  int size = conf.LinesPerFile;
  string? line = null;
  int lineIndex = 0;

  while((line = reader.ReadLine()) != null) {
    if (string.IsNullOrEmpty(line.Trim())) continue;

    builder.AppendLine(line);
    lineIndex++;
    if (lineIndex >= size) {
      string _tmpFileName = $"{ index.ToString().PadLeft(conf.IndexLength, '0') }-{ name }.txt";
      string _tmpFilePath = Path.Combine(tmpDir, _tmpFileName);
      string _content = builder.ToString();
      File.WriteAllText(_tmpFilePath, _content, encoding);

      builder.Clear();
      lineIndex = 0;
      index++;
    }
  }

  // 最后一章
  if(builder.Length > 0) {
    string _tmpFileName = $"{ index.ToString().PadLeft(conf.IndexLength, '0') }-{ name }.txt";
    string _tmpFilePath = Path.Combine(tmpDir, _tmpFileName);
    string _content = builder.ToString();
    File.WriteAllText(_tmpFilePath, _content, encoding);
  }

  pbarReadTxt.Tick();

} else {
  if (string.IsNullOrWhiteSpace(conf.Regex)) {
    Console.WriteLine("请提供解析章节的正则表达式");
    return;
  }

  var builder = new StringBuilder();
  using var stream = new FileStream(filePath, FileMode.Open);
  using var reader = new StreamReader(stream, encoding);

  long totalCount = stream.Length;
  long processedCount = 0;

  using var pbarReadTxt = mainBar.Spawn((int)totalCount, "读取解析小说文本", new ProgressBarOptions {
    ForegroundColor = ConsoleColor.Green,
    // BackgroundColor = ConsoleColor.DarkGreen,
    ProgressBarOnBottom = true,
    DisplayTimeInRealTime = false,
  });
  
  string? input = null;
  int index = conf?.StartIndex ?? 0;
  string title = conf!.Input;

  while((input = reader.ReadLine()) != null) {
    if(Regex.IsMatch(input, conf.Regex)) {
      // 解析
      Match m = Regex.Match(input, conf.Regex);
      string leftString = input[0..m.Index];
      // string leftString = input.Substring(0, m.Index);
      builder.AppendLine(leftString);
      // 写入文件
      string _tmpFileName = $"{ index.ToString().PadLeft(conf.IndexLength, '0') }-{ NormalizeTitle(title) }.txt";
      string _tmpFilePath = Path.Combine(tmpDir, _tmpFileName);
      string _content = builder.ToString();
      File.WriteAllText(_tmpFilePath, _content, encoding);
      
      // 更新缓存数据
      processedCount += encoding.GetBytes(_content).Length;
      pbarReadTxt.Tick((int)processedCount);

      builder.Clear();
      index++;
      
      title = m.Value;
      string rightString = input[m.Index..];
      // string rightString = input.Substring(m.Index);
      builder.AppendLine(rightString);
    } else {
      builder.AppendLine(input);
    }
  }

  // 最后一章
  if(builder.Length > 0) {
    string _tmpFileName = $"{ index.ToString().PadLeft(conf.IndexLength, '0') }-{ NormalizeTitle(title) }.txt";
    string _tmpFilePath = Path.Combine(tmpDir, _tmpFileName);
    string _content = builder.ToString();
    File.WriteAllText(_tmpFilePath, _content, encoding);
    processedCount += encoding.GetBytes(_content).Length;
    pbarReadTxt.Tick((int)processedCount);
  }
}
#endregion



LabelStep1:
mainBar.Tick();

if (args.Length > 0 && "step1".Equals(args[0], StringComparison.OrdinalIgnoreCase)) {
  Console.WriteLine("finished");
  mainBar.Tick();
  return;
}


#region 开始生成音频文件

var files = Directory.GetFiles(tmpDir);
var queue = new ConcurrentQueue<string>(files);

using var pbarGenerateAudio = mainBar.Spawn(files.Length, "生成音频文件", new ProgressBarOptions {
  ForegroundColor = ConsoleColor.Cyan,
  // BackgroundColor = ConsoleColor.DarkCyan,
  ProgressBarOnBottom = true,
  DisplayTimeInRealTime = false,
});


// 处理
int processCount = conf.Thread ?? 2;
string _newFileDirName = Path.Combine(currentDir, conf?.Output ?? "dest");
if (!Directory.Exists(_newFileDirName)) {
    Directory.CreateDirectory(_newFileDirName);
}
string exec = Path.Combine(appDir, @"libs\ffmpeg.exe");

var list = new List<Thread>();
for (var i = 0; i < processCount; i++) {
  var _th = new Thread(() => {
    string? _filePath = null;
    while (queue.TryDequeue(out _filePath)) {
      try {
        string _fileName = Path.GetFileNameWithoutExtension(_filePath);
        string _content = File.ReadAllText(_filePath, encoding).Trim();

        if (!string.IsNullOrEmpty(conf!.ReplaceRegex)) {
          _content = Regex.Replace(_content, conf.ReplaceRegex!, "");
        }

        if (string.IsNullOrEmpty(_content)) {
          _content = _fileName;
        }
        
        string _newFileNameWav = Path.Combine(_newFileDirName, _fileName + ".wav");
        string _newFileNameMp3 = Path.Combine(_newFileDirName, _fileName + ".mp3");

        ToWave(_newFileNameWav, _content, conf?.Speed ?? 8);

        if (File.Exists(_newFileNameMp3)) {
          File.Delete(_newFileNameMp3);
        }
        ProcessStart(exec, $"-i \"{_newFileNameWav}\" \"{_newFileNameMp3}\"");

        // 删除 wav 文件
        if (conf?.RemoveWAV ?? true) {
          File.Delete(_newFileNameWav);
        }
        pbarGenerateAudio.Tick();
      }
      catch (Exception ex) {
        Console.WriteLine(ex.Message);
      }
    }
  }) {
    IsBackground = false
  };

  _th.Start();
  list.Add(_th);
}


#endregion

while(true) {
  Thread.Sleep(1000);
  if(queue.IsEmpty && list.All(th => th.ThreadState == System.Threading.ThreadState.Stopped)) {
    break;
  }
}


if (conf?.RemoveTmpTxt ?? true) {
  Directory.Delete(tmpDir, true);
}
mainBar.Tick();
Console.WriteLine("finished");





#region 辅助函数

/// <summary>
/// 将文字转换为 WAV 音频
/// </summary>
/// <param name="file">wav 格式文件路径</param>
/// <param name="content">需要转换的文字</param>
/// <param name="rate">速度</param>
/// <returns></returns>
static void ToWave(string file, string content, int rate = 8) {
    if (File.Exists(file)) {
        File.Delete(file);
    }

    SpeechSynthesizer speech = new ();
    speech.SetOutputToDefaultAudioDevice();
    speech.SetOutputToWaveFile(file);

    //speech.Volume;
    //Console.WriteLine($"speech.Volume = {speech.Volume}"); 
    speech.Rate = rate;

    speech.Speak(content);
    speech.SetOutputToNull();

}


/// <summary>
/// 执行子命令
/// </summary>
/// <param name="cmd">命令</param>
/// <param name="args">参数</param>
/// <returns></returns>
static void ProcessStart(string cmd, string args) {
    // Console.WriteLine($"{ cmd } { args }");
    ProcessStartInfo info = new () {
      FileName = cmd,
      Arguments = args,

      CreateNoWindow = true,
      UseShellExecute = true,
      WindowStyle = ProcessWindowStyle.Hidden,

    };

    using Process? process = Process.Start(info);

    Task? t = process?.WaitForExitAsync(); // 等待任务结束
    
    t?.Wait();
}

/// <summary>
/// 格式化标题字符串
/// </summary>
/// <param name="title"></param>
/// <returns></returns>
static string NormalizeTitle(string title) {
  string s = title.Trim();
  string r = @"\\|/|:|\*|\t|\?|""|<|>|\|";
  return Regex.Replace(title.Trim(), r, "_");
}
#endregion

